#include "vertexbufferobject.h"
#include <GL/glext.h>
#include <iostream>

#ifdef _WINDOWS_
  #define glGetProcAddress wglGetProcAddress
#else
  #include <GL/glx.h>
  #define glGetProcAddress(x) glXGetProcAddressARB((const GLubyte*) (x))
#endif

PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = NULL;	
PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL;
PFNGLGENBUFFERSARBPROC glGenBuffersARB = NULL;
PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL;
#ifdef _WINDOWS_
  PFNGLDRAWRANGEELEMENTSPROC glDrawRangeElements = NULL;
#endif

VertexBufferObject::VertexBufferObject(VertexArray* vertexArray, bool ownerVertexArray)
: vertexArray(vertexArray), ownerVertexArray(ownerVertexArray)
{
    bufferIds[0] = 0;
    bufferIds[1] = 0;
    
    verticesSize = vertexArray->getVerticesSize();
    indicesSize = vertexArray->getIndicesSize();
    
    if (!glDeleteBuffersARB)
    {
        glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)glGetProcAddress("glDeleteBuffersARB");
        std::cout << "glDeleteBuffersARB = " << glDeleteBuffersARB << std::endl;
    }
    if (!glBindBufferARB)
    {
        glBindBufferARB = (PFNGLBINDBUFFERARBPROC)glGetProcAddress("glBindBufferARB");
        std::cout << "glBindBufferARB = " << glBindBufferARB << std::endl;
    }
    if (!glGenBuffersARB)
    {
        glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)glGetProcAddress("glGenBuffersARB");
        std::cout << "glGenBuffersARB = " << glGenBuffersARB << std::endl;
    }
    if (!glBufferDataARB)
    {
        glBufferDataARB = (PFNGLBUFFERDATAARBPROC)glGetProcAddress("glBufferDataARB");
        std::cout << "glBufferDataARB = " << glBufferDataARB << std::endl;
    }
#ifdef _WINDOWS_
    if (!glDrawRangeElements)
    {
        glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)glGetProcAddress("glDrawRangeElements");
        std::cout << "glDrawRangeElements = " << glDrawRangeElements << std::endl;
    }
#endif
}

VertexBufferObject::~VertexBufferObject()
{
    glDeleteBuffersARB(2, bufferIds);
    if (ownerVertexArray && vertexArray)
        delete vertexArray;
}

void VertexBufferObject::setVertexArray(VertexArray* vertexArray)
{
    // Replace the vertex array with the new instance
    if (vertexArray != this->vertexArray) {
        if (ownerVertexArray && this->vertexArray)
            delete this->vertexArray;
        this->vertexArray = vertexArray;
    }
    
    // Update the buffer ids for the new vertex array
    if (vertexArray && vertexArray->getVerticesSize() > 0 && verticesSize == 0)
        glGenBuffersARB(1, &bufferIds[0]);
    if ((!vertexArray || vertexArray->getVerticesSize() == 0) && verticesSize > 0)
        glDeleteBuffersARB(1, &bufferIds[0]);
    if (vertexArray && vertexArray->getIndicesSize() > 0 && indicesSize == 0)
        glGenBuffersARB(1, &bufferIds[1]);
    if ((!vertexArray || vertexArray->getIndicesSize() == 0) && indicesSize > 0)
        glDeleteBuffersARB(1, &bufferIds[1]);
    
    verticesSize = (vertexArray ? vertexArray->getVerticesSize() : 0);
    indicesSize = (vertexArray ? vertexArray->getIndicesSize() : 0);
}

void VertexBufferObject::generate()
{
    if (verticesSize > 0 && indicesSize > 0)
        glGenBuffersARB(2, bufferIds);
    else if (verticesSize > 0)
        glGenBuffersARB(1, &bufferIds[0]);
    else if (indicesSize > 0)
        glGenBuffersARB(1, &bufferIds[1]);
}

void VertexBufferObject::send(bool deleteVertexArray)
{
    if (!vertexArray)
        return;
    
    if (verticesSize > 0) {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, bufferIds[0]);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, verticesSize * sizeof(PackedVecColor), vertexArray->getVertices(), GL_STATIC_DRAW_ARB);
    }
    
    if (indicesSize > 0) {
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, bufferIds[1]);
        glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indicesSize * sizeof(GLuint), vertexArray->getIndices(), GL_STATIC_DRAW_ARB);
    }
    
    if (ownerVertexArray && deleteVertexArray) {
        delete vertexArray;
        vertexArray = NULL;
    }
}

void VertexBufferObject::setPointers()
{
    // If no vertices are present, this disables the VBO for vertices
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, bufferIds[0]);
    
    if (verticesSize == 0)
        return;
    
    GLsizei stride = sizeof(PackedVecColor);
    
    glColorPointer(4, GL_UNSIGNED_BYTE, stride, (GLfloat *)NULL + 3);
    glVertexPointer(3, GL_FLOAT, stride, (GLfloat *)NULL + 0);
}

void VertexBufferObject::bind()
{
    if (verticesSize > 0)
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, bufferIds[0]);
    if (indicesSize > 0)
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, bufferIds[1]);
}

void VertexBufferObject::enableClientStates()
{
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
}

void VertexBufferObject::disableClientStates()
{
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);
}

void VertexBufferObject::unbindBuffers()
{
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}

void VertexBufferObject::draw(GLenum mode)
{
    if (indicesSize > 0)
        draw(mode, 0, indicesSize);
    else if (verticesSize > 0)
        draw(mode, 0, verticesSize);
}

void VertexBufferObject::draw(GLenum mode, unsigned location)
{
    if (indicesSize > 0)
        draw(mode, location, indicesSize - location);
    else if (verticesSize > 0)
        draw(mode, location, verticesSize - location);
}

void VertexBufferObject::draw(GLenum mode, unsigned location, unsigned length)
{
    if (indicesSize > 0)
        glDrawRangeElements(mode, 0, verticesSize, length, GL_UNSIGNED_INT, (GLuint *)NULL + location);
    else if (verticesSize > 0)
        glDrawArrays(mode, location, length);
}

void VertexBufferObject::oneShotDraw(GLenum mode)
{
    setPointers();
    bind();
    draw(mode);
}

void VertexBufferObject::oneShotDraw(GLenum mode, unsigned location)
{
    setPointers();
    bind();
    draw(mode, location);
}

void VertexBufferObject::oneShotDraw(GLenum mode, unsigned location, unsigned length)
{
    setPointers();
    bind();
    draw(mode, location, length);
}
